﻿using System;
using System.Collections.Generic;
using System.Text;

namespace MouldingAndMillwork.SSIS
{
    public partial class SSISUtility
    {
        #region CLASS: Hashing
        public class Hashing
        {
            #region Public Enumerations
            public enum HashAlgorithm
            {
                Jenkins_One_At_A_Time,
                Paul_Hsieh_SuperFastHash,
                Austin_Appleby_Murmur2Hash  // SUSPECT!  Causing me problems...
            }
            #endregion

            #region Set/Store Hashing Algorithm
            private static HashAlgorithm _hashAlgorithm = HashAlgorithm.Jenkins_One_At_A_Time;

            public static void SetHashingAlgorithm(HashAlgorithm algorithm)
            {
                Hashing._hashAlgorithm = algorithm;
            }
            #endregion

            #region Hash Method
            public static UInt32 Hash(byte[] dataToHash)
            {
                UInt32 hashResult = 0;

                switch (Hashing._hashAlgorithm)
                {
                    case HashAlgorithm.Jenkins_One_At_A_Time:
                        hashResult = Hashing.JenkinsOneAtATime(dataToHash);
                        break;

                    case HashAlgorithm.Paul_Hsieh_SuperFastHash:
                        hashResult = Hashing.SuperFast(dataToHash);
                        break;

                    case HashAlgorithm.Austin_Appleby_Murmur2Hash:
                        hashResult = Hashing.Murmur2(dataToHash);
                        break;
                }

                return hashResult;
            }

            public static string HashFunctionInformationLink(HashAlgorithm algorithm)
            {
                switch (algorithm)
                {
                    case HashAlgorithm.Jenkins_One_At_A_Time:
                        return "http://www.burtleburtle.net/bob/hash/doobs.html";

                    case HashAlgorithm.Paul_Hsieh_SuperFastHash:
                        return "http://www.azillionmonkeys.com/qed/hash.html";

                    case HashAlgorithm.Austin_Appleby_Murmur2Hash:
                        return "http://murmurhash.googlepages.com/";
                }
                return "";
            }
            #endregion

            #region Specific Implementations of Hash
            #region Jenkins' One at a Time
            private static UInt32 JenkinsOneAtATime(byte[] dataToHash)
            {
                UInt32 hash = 0;
                int i;

                for (i = 0; i < dataToHash.Length; i++)
                {
                    hash += dataToHash[i];
                    hash += (hash << 10);
                    hash ^= (hash >> 6);
                }
                hash += (hash << 3);
                hash ^= (hash >> 11);
                hash += (hash << 15);

                return hash;
            }
            #endregion

            #region Paul Hsieh's SuperFastHash
            /// <summary>
            /// A C# porting of Paul Hsieh's SuperFastHash algorithm by Davy Landman (http://landman-code.blogspot.com)
            /// </summary>
            /// <param name="dataToHash"></param>
            /// <returns></returns>
            private static UInt32 SuperFast(Byte[] dataToHash)
            {
                Int32 dataLength = dataToHash.Length;
                if (dataLength == 0)
                    return 0;
                UInt32 hash = (UInt32)dataLength;
                Int32 remainingBytes = dataLength & 3; // mod 4
                Int32 numberOfLoops = dataLength >> 2; // div 4
                Int32 currentIndex = 0;
                while (numberOfLoops > 0)
                {
                    hash += (UInt16)(dataToHash[currentIndex++] | dataToHash[currentIndex++] << 8);
                    UInt32 tmp = (UInt32)((UInt32)(dataToHash[currentIndex++] | dataToHash[currentIndex++] << 8) << 11) ^ hash;
                    hash = (hash << 16) ^ tmp;
                    hash += hash >> 11;
                    numberOfLoops--;
                }

                switch (remainingBytes)
                {
                    case 3:
                        hash += (UInt16)(dataToHash[currentIndex++] | dataToHash[currentIndex++] << 8);
                        hash ^= hash << 16;
                        hash ^= ((UInt32)dataToHash[currentIndex]) << 18;
                        hash += hash >> 11;
                        break;
                    case 2:
                        hash += (UInt16)(dataToHash[currentIndex++] | dataToHash[currentIndex] << 8);
                        hash ^= hash << 11;
                        hash += hash >> 17;
                        break;
                    case 1:
                        hash += dataToHash[currentIndex];
                        hash ^= hash << 10;
                        hash += hash >> 1;
                        break;
                    default:
                        break;
                }

                /* Force "avalanching" of final 127 bits */
                hash ^= hash << 3;
                hash += hash >> 5;
                hash ^= hash << 4;
                hash += hash >> 17;
                hash ^= hash << 25;
                hash += hash >> 6;

                return hash;
            }
            #endregion

            #region Austin Appleby's MurmurHash
            /// <summary>
            /// A C# porting of Austin Appleby's MurmurHash algorithm by Davy Landman (http://landman-code.blogspot.com)
            /// </summary>
            /// <param name="dataToHash"></param>
            /// <returns></returns>
            private static UInt32 Murmur2(Byte[] dataToHash)
            {
                return Murmur2(dataToHash, 0xc58f1a7b);
            }
            const UInt32 m = 0x5bd1e995;
            const Int32 r = 24;

            private static UInt32 Murmur2(Byte[] dataToHash, UInt32 seed)
            {
                Int32 length = dataToHash.Length;
                if (length == 0)
                    return 0;
                UInt32 h = seed ^ (UInt32)length;
                Int32 currentIndex = 0;
                while (length >= 4)
                {
                    UInt32 k = (UInt32)(dataToHash[currentIndex++] | dataToHash[currentIndex++] << 8 | dataToHash[currentIndex++] << 16 | dataToHash[currentIndex++] << 24);
                    k *= m;
                    k ^= k >> r;
                    k *= m;

                    h *= m;
                    h ^= k;
                    length -= 4;
                }
                switch (length)
                {
                    case 3:
                        h ^= (UInt16)(dataToHash[currentIndex++] | dataToHash[currentIndex++] << 8);
                        h ^= (UInt32)(dataToHash[currentIndex] << 16);
                        h *= m;
                        break;
                    case 2:
                        h ^= (UInt16)(dataToHash[currentIndex++] | dataToHash[currentIndex] << 8);
                        h *= m;
                        break;
                    case 1:
                        h ^= dataToHash[currentIndex];
                        h *= m;
                        break;
                    default:
                        break;
                }

                // Do a few final mixes of the hash to ensure the last few
                // bytes are well-incorporated.

                h ^= h >> 13;
                h *= m;
                h ^= h >> 15;

                return h;
            }
            #endregion
            #endregion
        }
        #endregion
    }
}
